/*
 * Project Porcupine
 * Copyright (C) 2012  Team Porcupine
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.towerdive.porcupine.maps;

import com.towerdive.porcupine.controls.CreatureControl;
import com.towerdive.porcupine.controls.PlayerDamageControl;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Texture;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import jme3tools.optimize.GeometryBatchFactory;

/**
 * This is the server-side class that manages all the data during ground (a.k.a.
 * Roguelike) mode.  It will be extended by generators and for various
 * persistent environmental effects.
 * 
 * @author Martin "quill18" Glaude <quill18@towerdive.com>
 */
public abstract class GroundMap {
    private Set<MapObject> mapObjects;
    private Node playerNode;

    private static int nextId = 0;
    
    /**
     * The "root" node for all objects in the map.
     */
    private Node mapNode;

    public Node getMapNode() {
        return mapNode;
    }

    public void setMapNode(Node mapNode) {
        this.mapNode = mapNode;
    }
    
    public Set<MapObject> getMapObjects() {
        return mapObjects;
    }

    public Node getPlayerNode() {
        return playerNode;
    }
	
    public GroundMap(Node mapNode) {
        this.mapNode = mapNode;
        mapObjects = new HashSet<MapObject>();
    }

    abstract public float getTotalWidth();

    abstract public float getTotalHeight();

    public void PopulateScene(BulletAppState bulletAppState, AssetManager assetManager) throws Exception {
        
        for(Iterator iter = mapObjects.iterator(); iter.hasNext();) {
            MapObject map_object = (MapObject) iter.next();

            try {
                ProcessMapObject(map_object, bulletAppState, assetManager);
            } 
            catch (Exception ex) {
                //Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                throw ex;
            }		
        }
    }
    
    private void ProcessMapObject(MapObject mapObject, BulletAppState bulletAppState, AssetManager assetManager) throws Exception {
        // Hardcoding for testing
        switch(mapObject.object_type) {
            case FLOOR:
                mapNode.attachChild( GenerateFloor(mapObject, getTotalWidth(), getTotalHeight(), bulletAppState, assetManager) );
                break;
            case WALL:
                Spatial wall = GenerateWall(mapObject, bulletAppState, assetManager);
                mapNode.attachChild( wall );
                break;
            case PLAYER_START:
                playerNode = (Node) GeneratePlayer(mapObject, bulletAppState, assetManager);
                mapNode.attachChild( playerNode );
                break;
            case MONSTER:
                mapNode.attachChild( GenerateMonster(mapObject, bulletAppState, assetManager) );
                break;
            default:
                throw new Exception("Invalid ObjectType (or you forgot a break).");
        }
    }
   private Geometry GenerateFloor(MapObject mapObject, float width, float height, BulletAppState bulletAppState, AssetManager assetManager) {
        Quad g = new Quad(width, height);
        Geometry geom = new Geometry("Ground" + nextId++, g);
        geom.setName("ground");
        Material mat = new Material(assetManager,
        "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.White);

        Texture tex = assetManager.loadTexture("Textures/floors/RockSmooth0033_5_S.jpg");
        tex.setWrap(Texture.WrapMode.Repeat);
        mat.setTexture("ColorMap", tex);
        geom.getMesh().scaleTextureCoordinates(new Vector2f(50f,50f));

        geom.setMaterial(mat);
        geom.rotate(270*FastMath.DEG_TO_RAD, 0, 0);

        geom.setLocalTranslation(mapObject.x, mapObject.y, mapObject.z);

        RigidBodyControl floor_phy = new RigidBodyControl(0.0f);
        geom.addControl(floor_phy);
        bulletAppState.getPhysicsSpace().add(floor_phy);

        return geom;
    }

    private Spatial GenerateWall(MapObject mapObject, BulletAppState bulletAppState, AssetManager assetManager) {
        Spatial s = assetManager.loadModel("Models/wall.j3o");
        s.setName("Wall" + nextId++);
        Node n = new Node();
        n.attachChild(s);
        s.setLocalTranslation(0, -1, 0);
        
        CollisionShape shape = new BoxCollisionShape( new Vector3f(1f,1f,1f) );
        
        RigidBodyControl rbc = new RigidBodyControl(shape, 0);
        n.addControl(rbc);
        bulletAppState.getPhysicsSpace().add(rbc);
        
        rbc.setPhysicsLocation(new Vector3f(mapObject.x, mapObject.y+1f, mapObject.z));
                
        return n;
    }

    private Spatial GenerateMonster(MapObject mapObject, BulletAppState bulletAppState, AssetManager assetManager) {
        Node n = GenerateCreature("Models/cardboard_token.j3o", mapObject, bulletAppState, assetManager);
        n.setName("Monster" + nextId++);
        return n;        
    }

    private Spatial GeneratePlayer(MapObject mapObject, BulletAppState bulletAppState, AssetManager assetManager) {
        Node n = GenerateCreature("Models/hex_token.j3o", mapObject, bulletAppState, assetManager);
        n.setName("Player" + nextId++);

        return n;
    }

    private Spatial GenerateBrick(MapObject mapObject, BulletAppState bulletAppState, AssetManager assetManager) {
        Box box = new Box(Vector3f.ZERO, .5f, .5f, .5f);
        box.scaleTextureCoordinates(new Vector2f(1f, 2f));        

        Geometry brickGeo = new Geometry("brick", box);
        Material mat = new Material(assetManager,
        "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.White);
        brickGeo.setMaterial(mat);
        Texture tex = assetManager.loadTexture("Textures/floors/RockSmooth0033_5_S.jpg");
        tex.setWrap(Texture.WrapMode.Repeat);
        mat.setTexture("ColorMap", tex);		
        /** Position the brick geometry  */
        brickGeo.setLocalTranslation( new Vector3f(mapObject.x, mapObject.y+5f, mapObject.z-4) );
//        rootNode.attachChild(brick_geo);
        /** Make brick physical with a mass > 0.0f. */
        RigidBodyControl brickPhy = new RigidBodyControl(1f);
        /** Add physical brick to physics space. */
        brickGeo.addControl(brickPhy);
        bulletAppState.getPhysicsSpace().add(brickPhy);

        return brickGeo;
    }

    private Node GenerateCreature(String model_path, MapObject mapObject, BulletAppState bulletAppState, AssetManager assetManager) {
        /**
         * Load our mesh
         */
        Node s = (Node) assetManager.loadModel(model_path);
        /**
         * Create a player node, which will be the parent to the actual mesh
         * and which will be used for actual logic/physics.
         */
        Node n = new Node("creatureNode");
        n.attachChild(s);
        n.addControl(new PlayerDamageControl());
        n.addControl(new CreatureControl(mapNode));
        
        
        /**
         * Our capsule's actual height is height + 2*radius.
         * i.e. height is the space between the semicircles.
         */
        CapsuleCollisionShape collisionShape = new CapsuleCollisionShape(1f, 1f);
        
        /**
         * Make sure our capsule is not clipping into the ground, or it will
         * either "jump" upwards or simply fall through the floor.
         */
        n.setLocalTranslation(mapObject.x, mapObject.y+1.5f, mapObject.z);
        
        /**
         * Move our mesh down, since the origin (0,0,0) should be at
         * ground level.
         */
        s.setLocalTranslation(0, -1.5f, 0);

        /**
         * Activate the character controller in physics space
         */
        CharacterControl character = new CharacterControl(collisionShape, 0.05f);
        n.addControl(character);
        bulletAppState.getPhysicsSpace().add(character);
        
        return n;
    }

}
